

 /*
 This file '_spinner' is part of Firebird Integrated Solution 1.0

 Copyright (c) 2015 Lincong

 Contact:  
        Email: lincong1987@gmail.com

        QQ: 159257119
 
 See Usage at http://www.jplatformx.com/firebird

 Create date: 2015-07-14 04:47
 */


 (function ($) {

     function modifier(fn) {
         return function () {
             var previous = this.element.val();
             fn.apply(this, arguments);
             this._refresh();
             if (previous !== this.element.val()) {
                 this._trigger("change");
             }
         };
     }

     $.widget("ui.spinner", {
         version: "1.9.2",
         defaultElement: "<input>",
         widgetEventPrefix: "spin",
         options: {
             culture: null,
             icons: {
                 down: "ui-icon-triangle-1-s",
                 up: "ui-icon-triangle-1-n"
             },
             incremental: true,
             max: null,
             min: null,
             numberFormat: null,
             page: 10,
             step: 1,

             change: null,
             spin: null,
             start: null,
             stop: null
         },

         _create: function () {
             // handle string values that need to be parsed
             this._setOption("max", this.options.max);
             this._setOption("min", this.options.min);
             this._setOption("step", this.options.step);

             // format the value, but don't constrain
             this._value(this.element.val(), true);

             this._draw();
             this._on(this._events);
             this._refresh();

             // turning off autocomplete prevents the browser from remembering the
             // value when navigating through history, so we re-enable autocomplete
             // if the page is unloaded before the widget is destroyed. #7790
             this._on(this.window, {
                 beforeunload: function () {
                     this.element.removeAttr("autocomplete");
                 }
             });
         },

         _getCreateOptions: function () {
             var options = {},
                 element = this.element;

             $.each(["min", "max", "step"], function (i, option) {
                 var value = element.attr(option);
                 if (value !== undefined && value.length) {
                     options[option] = value;
                 }
             });

             return options;
         },

         _events: {
             keydown: function (event) {
                 if (this._start(event) && this._keydown(event)) {
                     event.preventDefault();
                 }
             },
             keyup: "_stop",
             focus: function () {
                 this.previous = this.element.val();
             },
             blur: function (event) {
                 if (this.cancelBlur) {
                     delete this.cancelBlur;
                     return;
                 }

                 this._refresh();
                 if (this.previous !== this.element.val()) {
                     this._trigger("change", event);
                 }
             },
             mousewheel: function (event, delta) {
                 if (!delta) {
                     return;
                 }
                 if (!this.spinning && !this._start(event)) {
                     return false;
                 }

                 this._spin((delta > 0 ? 1 : -1) * this.options.step, event);
                 clearTimeout(this.mousewheelTimer);
                 this.mousewheelTimer = this._delay(function () {
                     if (this.spinning) {
                         this._stop(event);
                     }
                 }, 100);
                 event.preventDefault();
             },
             "mousedown .ui-spinner-button": function (event) {
                 var previous;

                 // We never want the buttons to have focus; whenever the user is
                 // interacting with the spinner, the focus should be on the input.
                 // If the input is focused then this.previous is properly set from
                 // when the input first received focus. If the input is not focused
                 // then we need to set this.previous based on the value before spinning.
                 previous = this.element[0] === this.document[0].activeElement ?
                     this.previous : this.element.val();
                 function checkFocus() {
                     var isActive = this.element[0] === this.document[0].activeElement;
                     if (!isActive) {
                         this.element.focus();
                         this.previous = previous;
                         // support: IE
                         // IE sets focus asynchronously, so we need to check if focus
                         // moved off of the input because the user clicked on the button.
                         this._delay(function () {
                             this.previous = previous;
                         });
                     }
                 }

                 // ensure focus is on (or stays on) the text field
                 event.preventDefault();
                 checkFocus.call(this);

                 // support: IE
                 // IE doesn't prevent moving focus even with event.preventDefault()
                 // so we set a flag to know when we should ignore the blur event
                 // and check (again) if focus moved off of the input.
                 this.cancelBlur = true;
                 this._delay(function () {
                     delete this.cancelBlur;
                     checkFocus.call(this);
                 });

                 if (this._start(event) === false) {
                     return;
                 }

                 this._repeat(null, $(event.currentTarget).hasClass("ui-spinner-up") ? 1 : -1, event);
             },
             "mouseup .ui-spinner-button": "_stop",
             "mouseenter .ui-spinner-button": function (event) {
                 // button will add ui-state-active if mouse was down while mouseleave and kept down
                 if (!$(event.currentTarget).hasClass("ui-state-active")) {
                     return;
                 }

                 if (this._start(event) === false) {
                     return false;
                 }
                 this._repeat(null, $(event.currentTarget).hasClass("ui-spinner-up") ? 1 : -1, event);
             },
             // TODO: do we really want to consider this a stop?
             // shouldn't we just stop the repeater and wait until mouseup before
             // we trigger the stop event?
             "mouseleave .ui-spinner-button": "_stop"
         },

         _draw: function () {
             var uiSpinner = this.uiSpinner = this.element
                 .addClass("ui-spinner-input")
                 .attr("autocomplete", "off")
                 .wrap(this._uiSpinnerHtml())
                 .parent()
                 // add buttons
                 .append(this._buttonHtml());

             this.element.attr("role", "spinbutton");

             // button bindings
             this.buttons = uiSpinner.find(".ui-spinner-button")
                 .attr("tabIndex", -1)
                 .button()
                 .removeClass("ui-corner-all");

             // IE 6 doesn't understand height: 50% for the buttons
             // unless the wrapper has an explicit height
             if (this.buttons.height() > Math.ceil(uiSpinner.height() * 0.5) &&
                 uiSpinner.height() > 0) {
                 uiSpinner.height(uiSpinner.height());
             }

             // disable spinner if element was already disabled
             if (this.options.disabled) {
                 this.disable();
             }
         },

         _keydown: function (event) {
             var options = this.options,
                 keyCode = $.ui.keyCode;

             switch (event.keyCode) {
                 case keyCode.UP:
                     this._repeat(null, 1, event);
                     return true;
                 case keyCode.DOWN:
                     this._repeat(null, -1, event);
                     return true;
                 case keyCode.PAGE_UP:
                     this._repeat(null, options.page, event);
                     return true;
                 case keyCode.PAGE_DOWN:
                     this._repeat(null, -options.page, event);
                     return true;
             }

             return false;
         },

         _uiSpinnerHtml: function () {
             return "<span class='ui-spinner ui-widget ui-widget-content ui-corner-all'></span>";
         },

         _buttonHtml: function () {
             return "" +
                 "<a class='ui-spinner-button ui-spinner-up ui-corner-tr'>" +
                 "<span class='ui-icon " + this.options.icons.up + "'>&#9650;</span>" +
                 "</a>" +
                 "<a class='ui-spinner-button ui-spinner-down ui-corner-br'>" +
                 "<span class='ui-icon " + this.options.icons.down + "'>&#9660;</span>" +
                 "</a>";
         },

         _start: function (event) {
             if (!this.spinning && this._trigger("start", event) === false) {
                 return false;
             }

             if (!this.counter) {
                 this.counter = 1;
             }
             this.spinning = true;
             return true;
         },

         _repeat: function (i, steps, event) {
             i = i || 500;

             clearTimeout(this.timer);
             this.timer = this._delay(function () {
                 this._repeat(40, steps, event);
             }, i);

             this._spin(steps * this.options.step, event);
         },

         _spin: function (step, event) {
             var value = this.value() || 0;

             if (!this.counter) {
                 this.counter = 1;
             }

             value = this._adjustValue(value + step * this._increment(this.counter));

             if (!this.spinning || this._trigger("spin", event, {value: value}) !== false) {
                 this._value(value);
                 this.counter++;
             }
         },

         _increment: function (i) {
             var incremental = this.options.incremental;

             if (incremental) {
                 return $.isFunction(incremental) ?
                     incremental(i) :
                     Math.floor(i * i * i / 50000 - i * i / 500 + 17 * i / 200 + 1);
             }

             return 1;
         },

         _precision: function () {
             var precision = this._precisionOf(this.options.step);
             if (this.options.min !== null) {
                 precision = Math.max(precision, this._precisionOf(this.options.min));
             }
             return precision;
         },

         _precisionOf: function (num) {
             var str = num.toString(),
                 decimal = str.indexOf(".");
             return decimal === -1 ? 0 : str.length - decimal - 1;
         },

         _adjustValue: function (value) {
             var base, aboveMin,
                 options = this.options;

             // make sure we're at a valid step
             // - find out where we are relative to the base (min or 0)
             base = options.min !== null ? options.min : 0;
             aboveMin = value - base;
             // - round to the nearest step
             aboveMin = Math.round(aboveMin / options.step) * options.step;
             // - rounding is based on 0, so adjust back to our base
             value = base + aboveMin;

             // fix precision from bad JS floating point math
             value = parseFloat(value.toFixed(this._precision()));

             // clamp the value
             if (options.max !== null && value > options.max) {
                 return options.max;
             }
             if (options.min !== null && value < options.min) {
                 return options.min;
             }

             return value;
         },

         _stop: function (event) {
             if (!this.spinning) {
                 return;
             }

             clearTimeout(this.timer);
             clearTimeout(this.mousewheelTimer);
             this.counter = 0;
             this.spinning = false;
             this._trigger("stop", event);
         },

         _setOption: function (key, value) {
             if (key === "culture" || key === "numberFormat") {
                 var prevValue = this._parse(this.element.val());
                 this.options[key] = value;
                 this.element.val(this._format(prevValue));
                 return;
             }

             if (key === "max" || key === "min" || key === "step") {
                 if (typeof value === "string") {
                     value = this._parse(value);
                 }
             }

             this._super(key, value);

             if (key === "disabled") {
                 if (value) {
                     this.element.prop("disabled", true);
                     this.buttons.button("disable");
                 } else {
                     this.element.prop("disabled", false);
                     this.buttons.button("enable");
                 }
             }
         },

         _setOptions: modifier(function (options) {
             this._super(options);
             this._value(this.element.val());
         }),

         _parse: function (val) {
             if (typeof val === "string" && val !== "") {
                 val = window.Globalize && this.options.numberFormat ?
                     Globalize.parseFloat(val, 10, this.options.culture) : +val;
             }
             return val === "" || isNaN(val) ? null : val;
         },

         _format: function (value) {
             if (value === "") {
                 return "";
             }
             return window.Globalize && this.options.numberFormat ?
                 Globalize.format(value, this.options.numberFormat, this.options.culture) :
                 value;
         },

         _refresh: function () {
             this.element.attr({
                 "aria-valuemin": this.options.min,
                 "aria-valuemax": this.options.max,
                 // TODO: what should we do with values that can't be parsed?
                 "aria-valuenow": this._parse(this.element.val())
             });
         },

         // update the value without triggering change
         _value: function (value, allowAny) {
             var parsed;
             if (value !== "") {
                 parsed = this._parse(value);
                 if (parsed !== null) {
                     if (!allowAny) {
                         parsed = this._adjustValue(parsed);
                     }
                     value = this._format(parsed);
                 }
             }
             this.element.val(value);
             this._refresh();
         },

         _destroy: function () {
             this.element
                 .removeClass("ui-spinner-input")
                 .prop("disabled", false)
                 .removeAttr("autocomplete")
                 .removeAttr("role")
                 .removeAttr("aria-valuemin")
                 .removeAttr("aria-valuemax")
                 .removeAttr("aria-valuenow");
             this.uiSpinner.replaceWith(this.element);
         },

         stepUp: modifier(function (steps) {
             this._stepUp(steps);
         }),
         _stepUp: function (steps) {
             this._spin((steps || 1) * this.options.step);
         },

         stepDown: modifier(function (steps) {
             this._stepDown(steps);
         }),
         _stepDown: function (steps) {
             this._spin((steps || 1) * -this.options.step);
         },

         pageUp: modifier(function (pages) {
             this._stepUp((pages || 1) * this.options.page);
         }),

         pageDown: modifier(function (pages) {
             this._stepDown((pages || 1) * this.options.page);
         }),

         value: function (newVal) {
             if (!arguments.length) {
                 return this._parse(this.element.val());
             }
             modifier(this._value).call(this, newVal);
         },

         widget: function () {
             return this.uiSpinner;
         }
     });

 }(jQuery) );